import unit.Pair;
import unit.Quaternion;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Translate {
    private static final Translate translate = new Translate();

    private ArrayList<Quaternion> quaternionList;
    private final ArrayList<String> mipsList = new ArrayList<>();
    private final Map<String, Integer> varPoint = new HashMap<>();

    private int dataEnd = 4;
    //管理变量与字符串位置
    private int countVar = 0;
    private int countString = 0;

    //管理指针(数组首地址)位置
    private Map<String, Integer> pointTable = new HashMap<>();
    //记录指针最大偏移量
    private int countPointSize = 0;
    //储存二维数组第二维长度
    private Map<String, Integer> twoDTable = new HashMap<>();

    public Translate() {
    }

    public static Translate getInstance() {
        return translate;
    }

    public ArrayList<String> getMipsList() {
        return mipsList;
    }

    public void translateStart(ArrayList<Quaternion> quaternionList, Map<String, Integer> twoDTable) {
        this.quaternionList = quaternionList;
        this.twoDTable = twoDTable;
        mipsList.add(".data");
        mipsList.add("      array:.space 4000");
        mipsList.add("      point:.space 20000");
        mipsList.add("      save:.space 4000");
        mipsList.add("\n.text\n");
        mipsList.add(printMips("la", "$s1", "array"));
        mipsList.add(printMips("la", "$s2", "point"));
        mipsList.add(printMips("la", "$s3", "save"));
        mipsList.add(printMips("li", "$s4", "0"));
        //mipsList.add(printMips("j", "MainFunction"));
        /*
        for (Quaternion code : quaternionList) {
            code2mips(code);
        }
        */

        int signal1 = 0;
        int signal2 = 0;
        for (Quaternion code : quaternionList) {
            if (code.getType().equals("label")) {
                signal1 = quaternionList.indexOf(code);
                break;
            }
        }
        for (Quaternion code : quaternionList) {
            if (code.getType().equals("label") && code.getOne().equals("MainFunction")) {
                signal2 = quaternionList.indexOf(code);
                break;
            }
        }

        for (int i = 0; i < signal1; i++) {
            code2mips(quaternionList.get(i));
        }
        for (int i = signal2; i < quaternionList.size(); i++) {
            code2mips(quaternionList.get(i));
        }
        for (int i = signal1; i < signal2; i++) {
            code2mips(quaternionList.get(i));
        }

    }

    public void code2mips(Quaternion code) {
        if (code.getType().equals("=")) {
            move(code);
        } else if (code.getType().equals("bitand")) {
            bitand(code);
        } else if (code.getType().equals("+")) {
            addu(code);
        } else if (code.getType().equals("-")) {
            subu(code);
        } else if (code.getType().equals("*")) {
            mulu(code);
        } else if (code.getType().equals("/")) {
            div(code);
        } else if (code.getType().equals("%")) {
            model(code);
        } else if (code.getType().equals("==")) {
            seq(code);
        } else if (code.getType().equals("!=")) {
            sne(code);
        } else if (code.getType().equals("||")) {
            or(code);
        } else if (code.getType().equals("&&")) {
            and(code);
        } else if (code.getType().equals(">=")) {
            sge(code);
        } else if (code.getType().equals(">")) {
            sgt(code);
        } else if (code.getType().equals("<=")) {
            sle(code);
        } else if (code.getType().equals("<")) {
            slt(code);
        } else if (code.getType().equals("!")) {
            not(code);
        } else if (code.getType().equals("beqz")) {
            beqz(code);
        } else if (code.getType().equals("bnez")) {
            bnez(code);
        } else if (code.getType().equals("create point")) {
            createPoint(code);
        } else if (code.getType().equals("load point")) {
            loadPoint(code);
        } else if (code.getType().equals("save")) {
            save(code);
        } else if (code.getType().equals("recover")) {
            recover(code);
        } else if (code.getType().equals("j")) {
            mipsList.add(printMips("j", code.getOne()));
        } else if (code.getType().equals("printString")) {
            mipsList.add(dataEnd++,
                    "string" + String.valueOf(countString) + ":.asciiz \"" + code.getOne() + "\"");
            mipsList.add(printMips("la", "$a0", "string" + String.valueOf(countString++)));
            mipsList.add(printMips("li", "$v0", "4"));
            mipsList.add(printMips("syscall"));
        } else if (code.getType().equals("printInteger")) {
            if (isDigitOrMinus(code.getOne())) {
                mipsList.add(printMips("li", "$t1", code.getOne()));
            } else {
                lw(code.getOne(), "$t1");
            }
            mipsList.add(printMips("move", "$a0", "$t1"));
            mipsList.add(printMips("li", "$v0", "1"));
            mipsList.add(printMips("syscall"));
        } else if (code.getType().equals("label")) {
            mipsList.add(code.getOne() + ":");
        } else if (code.getType().equals("jal")) {
            mipsList.add(printMips("jal", code.getOne()));
        } else if (code.getType().equals("jr")) {
            mipsList.add(printMips("jr", code.getOne()));
        } else if (code.getType().equals("end")) {
            mipsList.add(printMips("li", "$v0", "10"));
            mipsList.add(printMips("syscall"));
        } else if (code.getType().equals("getint")) {
            mipsList.add(printMips("li", "$v0", "5"));
            mipsList.add(printMips("syscall"));
            sw(code.getOne(), "$v0");
        }
    }

    public void move(Quaternion code) {
        if (code.getOne().equals("ret")) {
            mipsList.add(printMips("move", "$v0", calNumber1(code.getTwo())));
        } else if (code.getOne().equals("$ra")) {
            mipsList.add(printMips("move", "$ra", calNumber1(code.getTwo())));
        } else if (code.getTwo().equals("$ra")) {
            sw(code.getOne(), "$ra");
        } else {
            sw(code.getOne(), calNumber1(code.getTwo()));
        }
    }

    public void save(Quaternion code) {
        lw(code.getOne(), "$t6");
        push("$t6");
    }

    public void recover(Quaternion code) {
        pop("$t6");
        sw(code.getOne(), "$t6");
    }

    public void push(String register) {
        mipsList.add(printMips("move", "$t0", "$s4"));
        mipsList.add(printMips("mul", "$t0", "$t0", "4"));
        mipsList.add(printMips("addu", "$t0", "$t0", "$s3"));
        mipsList.add(printMips("sw", register, "0($t0)"));
        mipsList.add(printMips("addi", "$s4", "$s4", "1"));
    }


    public void pop(String register) {
        mipsList.add(printMips("addi", "$s4", "$s4", "-1"));
        mipsList.add(printMips("move", "$t0", "$s4"));
        mipsList.add(printMips("mul", "$t0", "$t0", "4"));
        mipsList.add(printMips("addu", "$t0", "$t0", "$s3"));
        mipsList.add(printMips("lw", register, "0($t0)"));
    }

    public void createPoint(Quaternion code) {
        pointTable.put(code.getOne(), countPointSize);
        countPointSize += Integer.parseInt(code.getTwo());
    }

    public void loadPoint(Quaternion code) {
        String name = code.getOne();
        String point = code.getTwo();
        String offset = code.getThree();
        calculateOffset(point + "[" + offset + "]", "$t3");
        sw(name, "$t3");
    }


    public void addu(Quaternion code) {
        mipsList.add(printMips("addu", "$t3",
                calNumber1(code.getTwo()), calNumber2(code.getThree())));
        sw(code.getOne(), "$t3");
    }

    public void bitand(Quaternion code) {
        mipsList.add(printMips("and", "$t3",
                calNumber1(code.getTwo()), calNumber2(code.getThree())));
        sw(code.getOne(), "$t3");
    }

    public void subu(Quaternion code) {
        mipsList.add(printMips("subu", "$t3",
                calNumber1(code.getTwo()), calNumber2(code.getThree())));
        sw(code.getOne(), "$t3");
    }

    public void mulu(Quaternion code) {
        mipsList.add(printMips("mulu", "$t3",
                calNumber1(code.getTwo()), calNumber2(code.getThree())));
        sw(code.getOne(), "$t3");
    }

    public void div(Quaternion code) {
        mipsList.add(printMips("div", "$t3",
                calNumber1(code.getTwo()), calNumber2(code.getThree())));
        sw(code.getOne(), "$t3");
    }

    public void model(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("div", "$t3", one, two));
        mipsList.add(printMips("mfhi", "$t3"));
        sw(code.getOne(), "$t3");
    }

    public void seq(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("seq", "$t3", one, two));
        sw(code.getOne(), "$t3");
    }

    public void sne(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("sne", "$t3", one, two));
        sw(code.getOne(), "$t3");
    }

    public void or(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("sne", "$t1", one, "$0"));
        mipsList.add(printMips("sne", "$t2", two, "$0"));
        mipsList.add(printMips("or", "$t3", "$t1", "$t2"));
        sw(code.getOne(), "$t3");
    }

    public void and(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("sne", "$t1", one, "$0"));
        mipsList.add(printMips("sne", "$t2", two, "$0"));
        mipsList.add(printMips("and", "$t3", "$t1", "$t2"));
        sw(code.getOne(), "$t3");
    }

    public void sge(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("sge", "$t3", one, two));
        sw(code.getOne(), "$t3");
    }

    public void sgt(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("sgt", "$t3", one, two));
        sw(code.getOne(), "$t3");
    }

    public void sle(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("sle", "$t3", one, two));
        sw(code.getOne(), "$t3");
    }

    public void slt(Quaternion code) {
        String one = calNumber1(code.getTwo());
        String two = calNumber2(code.getThree());
        mipsList.add(printMips("slt", "$t3", one, two));
        sw(code.getOne(), "$t3");
    }

    public void not(Quaternion code) {
        String one = calNumber1(code.getTwo());
        mipsList.add(printMips("seq", "$t3", one, "$0"));
        sw(code.getOne(), "$t3");
    }

    public void beqz(Quaternion code) {
        String one = calNumber1(code.getOne());
        String two = code.getTwo();
        mipsList.add(printMips("beqz", one, two));
    }

    public void bnez(Quaternion code) {
        String one = calNumber1(code.getOne());
        String two = code.getTwo();
        mipsList.add(printMips("bnez", one, two));
    }

    public String calNumber1(String var) {
        return calNumber(var, "$t1");
    }

    public String calNumber2(String var) {
        return calNumber(var, "$t2");
    }

    public String calNumber(String var, String register) {
        if (isDigitOrMinus(var) && !var.equals("0")) {
            mipsList.add(printMips("li", register, var));
            return register;
        } else if (var.equals("0")) {
            return "$0";
        } else if (var.equals("ret")) {
            return "$v0";
        } else {
            lw(var, register);
            return register;
        }
    }

    //将一个变量由内存转移到某寄存器中
    public void lw(String var, String register) {
        if (!var.contains("[")) {
            if (!varPoint.containsKey(var)) {
                varPoint.put(var, countVar++);
            }
            int count = varPoint.get(var);
            mipsList.add(printMips("lw", register, count * 4 + "($s1)"));
        } else {
            calculateOffset(var, "$t9");
            mipsList.add(printMips("move", "$t0", "$t9"));
            mipsList.add(printMips("mul", "$t0", "$t0", "4"));
            mipsList.add(printMips("addu", "$t0", "$t0", "$s2"));
            mipsList.add(printMips("lw", register, "0($t0)"));
        }
    }

    //将某寄存器中的值存入变量对应的内存位置
    public void sw(String var, String register) {
        if (!var.contains("[")) {
            if (!varPoint.containsKey(var)) {
                varPoint.put(var, countVar++);
            }
            int count = varPoint.get(var);
            mipsList.add(printMips("sw", register, count * 4 + "($s1)"));
        } else {
            calculateOffset(var, "$t9");
            mipsList.add(printMips("move", "$t0", "$t9"));
            mipsList.add(printMips("mul", "$t0", "$t0", "4"));
            mipsList.add(printMips("addu", "$t0", "$t0", "$s2"));
            mipsList.add(printMips("sw", register, "0($t0)"));
        }
    }

    public void calculateOffset(String var, String register) {
        String name = var.substring(0, var.indexOf("["));
        ArrayList<Pair<Integer, Integer>> indexPairs = judgeNumberOfD(var);
        //计算数组偏移
        int start = indexPairs.get(0).getOne();
        int end = indexPairs.get(0).getTwo();
        String exp = var.substring(start + 1, end);
        if (indexPairs.size() == 1 && !twoDTable.containsKey(name)) {
            if (isDigitOrMinus(exp)) {
                mipsList.add(printMips("li", register, exp));
            } else {
                mipsList.add(printMips("move", register, calNumber(exp, "$t4")));
            }
        } else if (indexPairs.size() == 1 && twoDTable.containsKey(name)) {
            if (isDigitOrMinus(exp)) {
                mipsList.add(printMips("li", register,
                        String.valueOf(Integer.parseInt(exp) * twoDTable.get(name))));
            } else {
                mipsList.add(printMips("move", register, calNumber(exp, "$t4")));
                mipsList.add(printMips("mul",
                        register, register, String.valueOf(twoDTable.get(name))));
            }
        } else if (indexPairs.size() == 2) {
            int start1 = indexPairs.get(0).getOne();
            int end1 = indexPairs.get(0).getTwo();
            int start2 = indexPairs.get(1).getOne();
            int end2 = indexPairs.get(1).getTwo();
            String exp1 = var.substring(start1 + 1, end1);
            String exp2 = var.substring(start2 + 1, end2);
            if (isDigitOrMinus(exp1) && isDigitOrMinus(exp2)) {
                mipsList.add(printMips("li", register, String.valueOf(Integer.parseInt(exp1)
                        * twoDTable.get(name) + Integer.parseInt(exp2))));
            } else if (!isDigitOrMinus(exp1) && isDigitOrMinus(exp2)) {
                calNumber(exp1, "$t4");
                mipsList.add(printMips("move", register, "$t4"));
                mipsList.add(printMips("mul", register, register,
                        String.valueOf(twoDTable.get(name))));
                mipsList.add(printMips("addi", register, register, exp2));
            } else if (isDigitOrMinus(exp1) && !isDigitOrMinus(exp2)) {
                calNumber(exp2, "$t4");
                mipsList.add(printMips("li", register, String.valueOf(
                        Integer.parseInt(exp1) * twoDTable.get(name)
                )));
                mipsList.add(printMips("addu", register, register, "$t4"));
            } else {
                calNumber(exp1, "$t4");
                push("$t4");
                calNumber(exp2, "$t5");
                pop("$t4");
                mipsList.add(printMips("move", register, "$t4"));
                mipsList.add(printMips("mul", register, register,
                        String.valueOf(twoDTable.get(name))));
                mipsList.add(printMips("addu", register, register, "$t5"));
            }
        }

        //计算数组起始地址
        if (pointTable.containsKey(name)) {
            mipsList.add(printMips("li", "$t5", String.valueOf(pointTable.get(name))));
        } else {
            lw(name, "$t5");
        }

        mipsList.add(printMips("addu", register, register, "$t5"));
    }

    public static int count(String str, String word) {
        int ret = 0;
        for (int i = 0; i < str.length() - word.length() + 1; i++) {
            if (str.substring(i, i + word.length()).equals(word)) {
                ret++;
            }
        }
        return ret;
    }

    public ArrayList<Pair<Integer, Integer>> judgeNumberOfD(String var) {
        int signal = 0;
        int start = 0, end = 0;
        ArrayList<Pair<Integer, Integer>> indexPairs = new ArrayList<>();
        for (int i = 0; i < var.length(); i++) {
            if (("[").equals(String.valueOf(var.charAt(i)))) {
                if (signal == 0) {
                    start = i;
                }
                signal++;
            } else if (("]").equals(String.valueOf(var.charAt(i)))) {
                signal--;
                if (signal == 0) {
                    end = i;
                    indexPairs.add(new Pair<>(start, end));
                }
            }
        }
        return indexPairs;
    }

    public static boolean isDigitOrMinus(String str) {
        for (int i = 0; i < str.length(); i++) {
            if (!(Character.isDigit(str.charAt(i)) ||
                    "-".equals(String.valueOf(str.charAt(i))))) {
                return false;
            }
        }
        return true;
    }


    public String printMips(String type) {
        return type;
    }

    public String printMips(String type, String one) {
        return type + "      " + one;
    }

    public String printMips(String type, String one, String two) {
        return type + "      " + one + ", " + two;
    }

    public String printMips(String type, String one, String two, String three) {
        return type + "      " + one + ", " + two + ", " + three;
    }

}
